#include "Model.h"
#include "Mesh.h"
#include "TextureImage.h"

// _________ Konstrucktoren __________
Model::Model(char * name = "undefined"):Object(name)
{
	position = glm::vec3(0,0,0);
	currentModelId++;
	modelId = currentModelId;
	printf("Model %s (ID: %i) created \n", name, modelId);
}

Model::Model(Mesh m, char * name = "undefined"):Object(name)
{
	modelMesh = m;
	position = glm::vec3(0,0,0);
	currentModelId++;
	modelId = currentModelId;
	printf("Model %s (ID: %i) created \n", name, modelId);
}

Model::Model(char * path, char * name = "undefined"):Object(name)
{
	Mesh newMesh;
	newMesh.LoadMesh(path);
	
	modelMesh = newMesh;

	position = glm::vec3(0,0,0);
	currentModelId++;
	modelId = currentModelId;

	printf("Model %s (ID: %i) created \n", name, modelId);

}

Model::Model(char * path, GLuint texture, char * name = "undefined"):Object(name)
{
	Mesh newMesh;
	newMesh.LoadMesh(path);
	
	modelMesh = newMesh;

	diffuseTexture = texture;
	
	textureAvailable = true;
	
	position = glm::vec3(0,0,0);
	currentModelId++;
	modelId = currentModelId;

	printf("Model %s (ID: %i) created \n", name, modelId);
}

Model::Model(char * path, char * texturePath, char * name = "undefined"):Object(name)
{
	Mesh newMesh;
	newMesh.LoadMesh(path);
	modelMesh = newMesh;

	textureAvailable = true;
	GLuint newTexture = loadDDS(texturePath);
	//TextureImage img;
	//img.loadBMP("Mat/uvmap.bmp");
	//diffuseTexture = img.getTexureID();
	diffuseTexture = newTexture;

	position = glm::vec3(0,0,0);
	currentModelId++;
	modelId = currentModelId;

	printf("Model %s (ID: %i) created \n", name, modelId);
}


//___________ Funktionen _____________

bool Model::Init()
{
	//	ShadowMapping Technique
	m_shadowMappingTechnique = new ShadowMappingTechnique();

	if(!m_shadowMappingTechnique->Init())
	{
		std::cout << "Error initializing the shadowMapping technique" << std::endl;
		if (std::cin.get() == '\n')		
			return false;
	}

	m_shadowMappingTechnique->Enable();
	m_shadowMappingTechnique->SetTextureUnit(0);


	//	Depth Technique
	m_depthTechnique = new DepthTechnique();

	if(!m_depthTechnique->Init())
	{
		std::cout << "Error initializing the DepthTechnique" << std::endl;
		if (std::cin.get() == '\n')		
			return false;
	}

	return true;
}

void Model::render(mat4 ProjectionMatrix, mat4 ViewMatrix)
{
	//	Light
	glm::vec3 lightInvDir = glm::vec3(0.5f,2,2);

	// Use our shader
	m_shadowMappingTechnique->Enable();

	glm::mat4 ModelMatrix = glm::mat4(1.0);
	ModelMatrix = glm::translate(mat4(), getPosition());

	glm::mat4 MVP;

	//	if there is a parent make position relativ to its
	if(parent)
	{
		glm::mat4 ParentModelMatrix = glm::mat4(1.0);
		ParentModelMatrix = glm::translate(mat4(), parent->getPosition());
		MVP = ProjectionMatrix * ViewMatrix * ParentModelMatrix * ModelMatrix;

		MVP = ProjectionMatrix * ViewMatrix * ParentModelMatrix * ModelMatrix;
	}
	else
	{
		MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;
	}

	glm::mat4 biasMatrix(
		0.5, 0.0, 0.0, 0.0, 
		0.0, 0.5, 0.0, 0.0,
		0.0, 0.0, 0.5, 0.0,
		0.5, 0.5, 0.5, 1.0
		);

	glm::mat4 depthBiasMVP = biasMatrix * allDepthMVP[modelId];

	// Send our transformation to the currently bound shader in the "MVP" uniform
	m_shadowMappingTechnique->setMatrixID(MVP);
	m_shadowMappingTechnique->setModelMatrixID(ModelMatrix);
	m_shadowMappingTechnique->setViewMatrixID(ViewMatrix);
	m_shadowMappingTechnique->setDepthBiasID(depthBiasMVP);
	
	//	Light
	m_shadowMappingTechnique->setlightInvDirID(lightInvDir);

	m_shadowMappingTechnique->setShadowMapID(1);

	// Set our "DiffuseTextureSampler" sampler to user Texture Unit 0
	m_shadowMappingTechnique->SetTextureUnit(0);
	
	if(textureAvailable == true)
	{
		// Bind our diffuse texture in Texture Unit 0
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, diffuseTexture);
	}
	else
	{
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, 0);
	}
	
	modelMesh.Render();

	//	Render all children
	for (Object* &m : children)
	{
		m->render(ProjectionMatrix, ViewMatrix);
	}

}

void Model::renderToFrameBuffer()
{
	//	Light
	glm::vec3 lightInvDir = glm::vec3(0.5f,2,2);

	//	Use the shader
	m_depthTechnique->Enable();

	// Compute the MVP matrix from the light's point of view
	glm::mat4 depthProjectionMatrix = glm::ortho<float>(-10,10,-10,10,-10,20);
	glm::mat4 depthViewMatrix = glm::lookAt(lightInvDir, glm::vec3(0,0,0), glm::vec3(0,1,0));
	// or, for spot light :
	//glm::vec3 lightPos(5, 20, 20);
	//glm::mat4 depthProjectionMatrix = glm::perspective<float>(45.0f, 1.0f, 2.0f, 50.0f);
	//glm::mat4 depthViewMatrix = glm::lookAt(lightPos, lightPos-lightInvDir, glm::vec3(0,1,0));	

	glm::mat4 depthModelMatrix = glm::mat4(1.0);
	depthModelMatrix = glm::translate(mat4(), getPosition());
	glm::mat4 depthMVP = depthProjectionMatrix * depthViewMatrix * depthModelMatrix;



	//	Save dephtMVP
	//allDepthMVP.push_back(depthMVP);
	allDepthMVP[modelId] = depthMVP;

	// Send our transformation to the currently bound shader, in the "MVP" uniform
	m_depthTechnique->setDepthMatrixID(depthMVP);

	modelMesh.RenderToFrameBuffer();

	//	Render all children
	for (Object* &m : children)
	{
		m->renderToFrameBuffer();
	}
}



void Model::debugRender() 
{
	glLineWidth(5);
	glColor3f(0,0,1);
	glBegin(GL_LINES);
			
		glVertex3f(position.x, position.y, position.z);
		glVertex3f(position.x+2, position.y, position.z);

		glVertex3f(position.x, position.y, position.z);
		glVertex3f(position.x-2, position.y, position.z);

		glVertex3f(position.x, position.y, position.z);
		glVertex3f(position.x, position.y+2, position.z);

		glVertex3f(position.x, position.y, position.z);
		glVertex3f(position.x, position.y-2, position.z);

		glVertex3f(position.x, position.y, position.z);
		glVertex3f(position.x, position.y, position.z+2);

		glVertex3f(position.x, position.y, position.z);
		glVertex3f(position.x, position.y, position.z-2);

	glEnd();
	glLineWidth(1);
}

//___________ Setter & Getter ____________
void Model::setDiffuseTexture(GLuint texture)
{
	diffuseTexture = texture;
}

void Model::setPosition(float x, float y, float z)
{
	position = glm::vec3(x, y, z);
}

glm::vec3 Model::getPosition()
{
	return position;
}

int Model::getModelId()
{
	return modelId;
}

//_____________ Destrucktor ________________
Model::~Model(void)
{
	std::cout << "Model " << name << " destroyed" << std::endl; 
}



int Model::currentModelId = 0;
std::map<int, glm::mat4> Model::allDepthMVP;